在創造各式各樣的物件時,有很多時候會發現怎麼重複的代碼很多。
為了解決這個問題,可以採用繼承與介面的方式。
繼承的文法
class Employee {}
class sales extends Employee {}
想像一間公司裡有許多員工,有業務有後勤有管理人員。這些職位的人員雖然都有各自的特性,但也都是員工的一種,他們應該會有員工編號之類的共用屬性。
例如sales類繼承了Employee類,就可以獲得裡面所有的變數與方法,不用重新寫一次。而且還可以追加新的方法,或是覆寫掉不喜歡的方法!
介面的文法
class Employee implements MyInterface {}
介面有點類似繼承,一樣可以獲得來自其他類裡面的變數和方法。不過兩者概念上有點不一樣。
繼承只能繼承一個類,概念像是一個Employee類可以升級為業務、後勤或是管理人員,擁有進階的能力。
而介面可以引用很多個,類似裝備或工具,可以隨時裝上或拆卸。
接下來介紹一下關於繼承的細節
被繼承的類又被稱為Super類或是父類,繼承類則被稱為Sub類或子類。
請看以下關於覆寫(override)的範例:
class Super {
public void print(String s){
System.out.println("Super class : " + s );
}
public void method(){}
}
class Sub extends Super {
public void print(String s){
System.out.println("Sub class : " + s );
}
//void method(){}
}
class Sample6_1 {
public static void main(String[] args) {
Super s1 = new Super();
s1.print("text"); //會調用Super類的方法
Sub s2 = new Sub();
s2.print("text"); //會調用Sub類的方法
}
}
執行結果
Super class : text
Sub class : text
可以發現Sub class裡面的print方法被複寫掉了。與Super class的執行結果不同。
要注意的是,覆寫Super類的方法時,使用一樣的名字和返回值類型,修飾詞則需一樣或者權限更開放。
(權限開放程度: public > protected > 不指定 > private)
例如本案例的method()在Super class裡面的修飾詞是public,則Sub class裡面的method()也必須是public,如果沒寫或寫其他修飾詞就會報錯。
final修飾詞可以用在變數前面,讓其成為無法被修改的定數。也可以放在方法前面,讓其無法被覆寫。如果是放在類的前面,則此類無法被繼承。如下例:
class Super {
final void method(){}
}
final class Super {}
this用來代稱本物件,用來解決變數代稱的問題。
例如有個有名的對話故事如下
「誰打我?」
誰:「我沒打人」
人:「我知道」
我:「知道什麼?」
程式範例如下
int id;
void setId(int id){
//id = id; //左右兩個id都是同一個變數(第二行的id)。
this.id = id; //this.id是指這個物件的id(第一行的id)。
}
this也可以用來減少重複寫代碼的問題,例如應用在建構式中,範例如下:
class Foo {
String s; int i;
public Foo(){
this("no_data");
}
public Foo(String s){
this(s,1);
}
public Foo(String s, int i){
this.s = s;this.i = i;
System.out.println("String : " + this.s);
System.out.println("int : " + this.i);
}
}
class Sample6_2 {
public static void main(String[] args) {
System.out.println("調用Foo()------");
Foo f1 = new Foo();
System.out.println("調用Foo(String s)------");
Foo f2 = new Foo("Tom");
System.out.println("調用Foo(String s, int i)------");
Foo f3 = new Foo("Mary",30);
}
}
執行結果
調用Foo()------
String : no_data
int : 1
調用Foo(String s)------
String : Tom
int : 1
調用Foo(String s, int i)------
String : Mary
int : 30
可以看到Foo裡面有三個建構式,分別對應無資料、只有文字、有文字及數值資料等三種狀況。
利用this指令,當無String資料時,將預設值("no_data")填入後,傳給第二個建構式。
在第二個建構式中,當無int值時,將預設值(1)填入後,然後傳給第三個建構式。
在第三個建構式中進行完整的處理。
利用這樣的結構,就不需要重複寫很多個類似的建構式了。
super表示父類,利用這個指令可以調用父類的方法或變數。
雖然子類一般都繼承了父類的方法,但是如果有覆寫的狀況,則可以利用super調用原本的父類方法。
class Super {
int num;
public void methodA(){num += 100;}
public void print(){System.out.println("num = " + num);}
}
class Sub extends Super {
public void methodA(){num += 500;}
public void methodB(int num){
methodA();
print();
super.methodA();
print();
}
}
class Sample6_3 {
public static void main(String[] args) {
Sub s = new Sub();
s.methodB(0);
}
}
執行結果
num = 500
num = 600
調用覆寫後的方法讓num增加了500
再調用父類原本的方法,讓num增加了100
當子類被實例化時,會先執行父類的建構式,再執行子類的建構式。
但是有個跟直覺不太一樣的地方,需要透過super指令來修正。看看以下範例:
class Super {
public Super(){System.out.println("Super()");}
public Super(int a){System.out.println("Super(int a)");}
}
class Sub extends Super {
public Sub(){System.out.println("Sub()");}
public Sub(int a){System.out.println("Sub(int a)");}
}
class Sample6_4 {
public static void main(String[] args) {
Sub s1 = new Sub();
Sub s2 = new Sub(10);
}
}
執行結果
Super()
Sub()
Super()
Sub(int a)
由結果的第一行及第二行可以發現,實例化物件時,確實先執行了父類的建構式,然後執行子類的建構式。
但是第三行為什麼不是super(int a)呢?
這是因為當子類被實例化時,預設會執行父類的無參數建構式(因為參數並未傳遞給父類)。
如果要避免這種現象,需要使用super指令,把參數丟給父類。如下例:
class Super {
public Super(){System.out.println("Super()");}
public Super(int a){System.out.println("Super(int a)");}
}
class Sub extends Super {
public Sub(){System.out.println("Sub()");}
public Sub(int a){
super(a);
System.out.println("Sub(int a)");
}
}
class Sample6_5 {
public static void main(String[] args) {
Sub s1 = new Sub();
Sub s2 = new Sub(10);
}
}
執行結果
Super()
Sub()
Super(int a)
Sub(int a)
在JAVA裡面,處理內容被詳細記載,可以實例化並使用的類稱為實例類。相對的,只記載了方法名稱,卻未填寫內容的類,被稱為抽象類。
想像一下,假如我們想設計一台吸塵器,但形式和內容都還不確定,只知道它需要110V的電,能夠吸灰塵。
那麼首先我們就先設計一個具有110V插頭、具有吸灰塵方法的class,至於詳細的內容,就等物件實例化時再填寫就好。
抽象類具有以下特點:
無法被實例化。要實例化需新增一個實例類,繼承了此抽象類之後,把所有抽象方法都覆寫成實例方法(記入內容)。
抽象類裡面可以寫抽象方法以及實例方法
抽象類也可以繼承抽象類。
抽象類和抽象方法的寫法,就是在最前面加上一個abstract修飾詞即可。
abstract class 類名{}
abstract 返回值 方法名(引數);
範例
abstract class Employee{}
class abstract Employee{} //報錯,abstract須放在最前面
abstract void funcA();
abstract void funcA(){}; //報錯,abstract方法不需要{}及其內容。
void abstract funcA(); //報錯,abstract須放在最前面
介面說起來有點像抽象類,裡面也放了一些抽象方法。不過不一樣的是,介面使用implements來引用,而非extends繼承。
請看以下範例:
interface MyInter1 {
double methodA(int num);
default void methodB(){System.out.println("methodB()");}
}
interface MyInter2 {
int methodC(int val1, int val2);
static void methodD(){System.out.println("methodD()");}
}
class MyClass implements MyInter1,MyInter2 {
public double methodA(int num){return num * 0.3;}
public int methodC(int val1, int val2){return val1 * val2;}
}
class Sample6_6 {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println("methodA() " + obj.methodA(10));
System.out.println("methodC() " + obj.methodC(10,20));
obj.methodB();
//obj.methodD(); //error
MyInter2.methodD();
}
}
執行結果
methodA() 3.0
methodC() 200
methodB()
methodD()
說明
methodA和methodC是抽象方法,在MyClass裡面被覆寫為實例方法而能執行。(注意覆寫時使用了public修飾詞,這是因為覆寫時的權限必須寬於抽象方法。)
methodB前面加了default修飾詞,這是因為interface類原本是只能寫抽象方法的,但JAVA SE8之後利用default修飾詞也可以寫實例方法。
methodD是static方法,看起來沒有傳遞給實例化物件使用,只能由interface呼叫。
在interface裡面,儲存值會自動加上public static final修飾詞。
在interface裡面,方法會自動加上public和abstract修飾詞
interface的繼承
interface簡稱為IF,也可以使用繼承,範例如下:
interface XIF {
void methodA();
}
interface YIF {
void methodB();
}
interface SubIF extends XIF, YIF{
void methodC();
}
class MyClass implements XIF,YIF {
public void methodA(){System.out.println("methodA()");}
public void methodB(){System.out.println("methodB()");}
public void methodC(){System.out.println("methodC()");}
}
class Sample6_7 {
public static void main(String[] args) {
MyClass c = new MyClass();
c.methodA();c.methodB();c.methodC();
}
}
以下兩點請注意:
interface可以繼承複數的interface(還記得class只能繼承一個class嗎?)
將抽象方法覆寫時,需使用pbulic修飾詞。
為了方便,有時JAVA會自動進行資料型態的變換。
由左至右,比較小的資料儲存型態可以轉換成比較大的資料儲存型態。
byte -> short -> int -> long -> float -> double
char -> int
如果要反向轉換的話,需使用()符號進行強制轉換。
範例
變數的自動轉換
short s = 10;
int i = s;
變數s會從short型自動轉換為int型態
引數的自動轉換
int i = 100;
method(i);
void method(double b){};
i雖然是int型態的資料,但是作為引數被丟入method時,可以自動轉換為double型。
返回值的自動轉換
double d = method();
int method(){int i = 100; return i;}
返回的int型資料,可以自動被轉換為double型資料填入變數d中。
變數的強制轉換
int i = 100;
short s = (short)i;
由大至小的轉換需要使用()符號進行強制轉換。
引數的強制轉換
double d = 10.5
method((int)d);
void method(int i){}
返回值的強制轉換
int i = method();
int method(){double d = 10.5; return (int)d;}
計算時的注意點:
兩個數值進行計算時,JAVA會自動把雙方變為一樣的資料型態。
如果原本的資料型態是byte,short等較小的資料型態,會被轉換成int型態之後計算。
範例
short s1 = 10;
s1 = ++s1; //可順利執行。因為沒有使用計算符,因此資料型態未被轉換。
s1 = s1 + 1; //NG,因為s1會被轉換為int型後+1,無法再放回short型的變數裡面。
解決例
s1 = short(s1 + 1);
除了數值外,物件也可以自動變換。
子類可以自動變換為父類。
實例類可以自動變換為抽象類。
這是什麼意思呢?
想像員工是一個父類,業務是一個子類。
由於業務保有員工的各種屬性,所以可以被當成員工來處理。
但如果是相反方向的強制轉換,則須使用()。
例:
class Super {}
class Sub extends Super {}
class Test {
Super super = new Sub(); //子類轉父類可自動轉換
Sub sub = (Sub)super; //父類轉子類須強制轉換
}
請看以下範例
class Super {
void methodA(){}
}
class Sub extends Super {
void methodA(){}
void methodB(){}
}
class Test {
Super super = new Sub();
super.methodA();
//super.methodB();
Sub sub = (Sub)super;
sub.methodB();
}
此範例中實例化了一個Sub物件,並轉存成Super形式。
super.methodA(); 這行執行的methodA,會是Sub class裡覆寫後的。
super.methodB(); 這行會報錯,因為super class裡面沒有methodB方法。
須將其轉回Sub形式,才能呼叫methodB方法。
舉個容易瞭解的例子,假設員工擁有「溝通(基礎)」這個技能,業務也有,但是是升級版的「溝通(進階)」。
這時你請這位員工執行此技能,就會執行出「溝通(進階)」(回不去了)。
但基於JAVA的保護機制,你無法讓一個人以員工身分執行業務專有的技能,例如「開發市場」。(即便他會)
必須要明白的告訴JAVA說,這個員工是個業務,才能夠使用業務的技能。
關於物件的轉換,有一些需要注意的點,請看下例:
class Super {}
class Sub extends Super {}
class Foo {}
Super obj1 = new Sub();
Sub sub1 = (Sub)obj1; //可正常執行。
Foo obj2 = new Foo();
Sub sub2 = (Sub)obj2; //NG,無繼承關係的物件無法轉換,編譯時會報錯。
Super obj3 = new Super();
Sub sub3 = (Sub)obj3; //NG,本質是Super的物件無法轉換為Sub物件,執行時會報錯。
以上案例說明了強制轉換只是一種聲明,並不能改變物件的本質。
所以本來是Sub類的物件,即使轉為Super,也可再轉回Sub。
但原本是Super的物件,無法轉換為Sub。
概念說明如下:
Sub(擁有100%能力) ->Super(被當成Super看待,部分能力受限)->Sub(擁有100%能力)
Super(擁有100%能力) ->Sub(被當成Sub看待,超出自己的能力範圍,因此NG)
原來只有超人才能變身成超人呀..
instance是實例的意思,可以判斷目標物件是否為特定的class(也包含父類或interface)
來看看範例:
interface A {}
interface B {}
class C {}
class D extends C implements B {}
class E {}
class Sample6_8 {
public static void main(String[] args) {
D d = new D();
System.out.println(d instanceof A);
System.out.println(d instanceof B);
System.out.println(d instanceof C);
System.out.println(d instanceof D);
//System.out.println(d instanceof E);
}
}
執行結果
false
true
true
true
以上是第六章 繼承與多型的學習心得,下一章會介紹API的使用。
參考教材: JAVAプログラマSilver SE8 - 山本 道子